﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows.Input;
using AppToolkit.WPF.UI.Search;
using NUnit.Framework;

namespace AppToolkit.WPF.Tests.UI.Search
{
    [TestFixture]
    public class SearchContextTests
    {
        SearchContext<string> _target;

        [SetUp]
        public void Setup()
        {
            _target = new SearchContext<string>();
        }

        [TearDown]
        public void Dispose()
        {
            _target = null;
        }

        /// <summary>
        /// Does provide search specification property.
        /// </summary>
        [Test]
        public void Can_set_SearchSpecification()
        {
            const string expectedValue = "test";
            _target.SearchSpecification = expectedValue;
            Assert.AreEqual(expectedValue, _target.SearchSpecification);
        }

        /// <summary>
        /// Does provide SearchNext command.
        /// </summary>
        [Test]
        public void SearchNext_is_disabled_when_no_SearchSpecification_is_set()
        {
            ICommand next = _target.SearchNextCommand;
            Assert.IsFalse(next.CanExecute(null));
        }

        /// <summary>
        /// Does provide SearchNext command.
        /// </summary>
        [Test]
        public void SearchNext_is_enabled_when_SearchSpecification_is_set()
        {
            initializeSearchContext();
            ICommand next = _target.SearchNextCommand;

            _target.SearchSpecification = "Test";

            Assert.IsTrue(next.CanExecute(null));
        }

        /// <summary>
        /// Does provide SearchNext command.
        /// </summary>
        [Test]
        public void Can_search_simple_string_list()
        {
            initializeSearchContext();
            string elementToSearch = _target.SourceCollection.ElementAt(5);

            ICommand next = _target.SearchNextCommand;

            _target.SearchSpecification = elementToSearch;

            string selectedItem = null;
            _target.SelectItem = (item) =>
            {
                selectedItem = item;
            };

            next.Execute(null);

            Assert.IsNotNull(selectedItem, "SelectItem was not called!");
            Assert.AreEqual(elementToSearch, selectedItem);
        }

        [Test]
        public void Can_search_10000_with_containes_operation()
        {
            initializeSearchContext();
            string elementToSearch = _target.SourceCollection.ElementAt(5);

            ICommand next = _target.SearchNextCommand;

            const string STR_ItemToLookFor = "itemToLookFor";
            _target.SearchSpecification = STR_ItemToLookFor;

            string selectedItem = null;
            _target.SelectItem = (item) =>
            {
                selectedItem = item;
            };

            List<string> srcCol = new List<string>();
            for (int i = 0; i < 10000; i++)
                srcCol.Add("rubish");
            srcCol.Add(STR_ItemToLookFor);

            _target.SourceCollection = srcCol;
            _target.FilterSetGeneration = (sourceCol, filterSpec) =>
                {
                    return sourceCol.Where(p => p.ToLowerInvariant().Contains(filterSpec.ToLowerInvariant()));
                };

            Stopwatch sw = Stopwatch.StartNew();
            next.Execute(null);
            sw.Stop();
            Trace.TraceInformation("Searching 10.000 items took:  {0}", sw.Elapsed);

            Assert.IsNotNull(selectedItem, "SelectItem was not called!");
            Assert.AreEqual(STR_ItemToLookFor, selectedItem);
            Assert.That(sw.Elapsed, Is.LessThan(TimeSpan.FromMilliseconds(100)));
        }

        /// <summary>
        /// SearchNext pressed multiple times skips through the Filtered set.
        /// </summary>
        [Test]
        public void SearchNext_skips_through_items_in_collection()
        {
            const string elementToSearch = "FFFF";
            initializeSearchContext(17, elementToSearch);

            ICommand next = _target.SearchNextCommand;

            _target.SearchSpecification = elementToSearch;

            string selectedItem = null;
            _target.SelectItem = (item) =>
            {
                selectedItem = item;
            };

            next.Execute(null);

            Assert.IsNotNull(selectedItem, "SelectItem was not called!");
            var firstElementFound = selectedItem;
            selectedItem = null;

            next.Execute(null);

            Assert.IsNotNull(selectedItem, "SelectItem was not called!");
            Assert.AreEqual(elementToSearch, selectedItem);
            Assert.AreNotSame(firstElementFound, selectedItem, "Did not skip to the next item!");
        }

        /// <summary>
        /// Provide error information in case no match was found.
        /// </summary>
        [Test]
        public void Does_return_an_error_in_case_no_item_was_found()
        {
            initializeSearchContext();
            string elementToSearch = "not to be found";

            ICommand next = _target.SearchNextCommand;

            _target.SearchSpecification = elementToSearch;

            string selectedItem = null;
            _target.SelectItem = (item) =>
            {
                selectedItem = item;
            };

            next.Execute(null);

            Assert.IsNull(selectedItem, "SelectItem must be null!");
            Assert.IsTrue(_target.HasError);
            Assert.AreEqual(ErrorInfo.ResultSetIsEmpty, _target.ErrorInfo);
        }

        /// <summary>
        /// Provide error information in case the last item was already reached.
        /// </summary>
        [Test]
        public void Does_return_an_error_in_case_last_item_was_already_reached()
        {
            initializeSearchContext();
            string elementToSearch = _target.SourceCollection.ElementAt(5);

            ICommand next = _target.SearchNextCommand;

            _target.SearchSpecification = elementToSearch;

            string selectedItem = null;
            _target.SelectItem = (item) =>
            {
                selectedItem = item;
            };

            next.Execute(null);
            next.Execute(null);

            Assert.IsNotNull(selectedItem, "SelectItem shall remain on first item found!");
            Assert.IsTrue(_target.HasError);
            Assert.AreEqual(ErrorInfo.LastItemReached, _target.ErrorInfo);
        }

        [Test]
        public void Reset_cached_results_in_case_SearchSpec_is_changing()
        {
            initializeSearchContext();
            string elementToSearch = _target.SourceCollection.ElementAt(5);

            ICommand next = _target.SearchNextCommand;

            _target.SearchSpecification = elementToSearch;

            string selectedItem = null;
            _target.SelectItem = (item) =>
            {
                selectedItem = item;
            };

            next.Execute(null);

            // reset SearchSpec, so that item is not found, in case cached results are not reset
            selectedItem = null;
            _target.SearchSpecification = _target.SourceCollection.ElementAt(3);

            next.Execute(null);

            Assert.IsNotNull(selectedItem, "SelectItem not found!");
            Assert.IsFalse(_target.HasError);
        }

        [Test]
        public void Forward_over_last_item_then_backward()
        {
            initializeSearchContext();
            string elementToSearch = _target.SourceCollection.ElementAt(5);

            ICommand next = _target.SearchNextCommand;
            ICommand prev = _target.SearchPrevCommand;

            _target.SearchSpecification = elementToSearch;

            string selectedItem = null;
            _target.SelectItem = (item) =>
            {
                selectedItem = item;
            };

            next.Execute(null);
            next.Execute(null);
            next.Execute(null);

            prev.Execute(null);

            Assert.IsNotNull(selectedItem, "SelectItem not found!");
            Assert.IsFalse(_target.HasError);
        }

        [Test]
        public void backward_over_first_item_then_forward()
        {
            initializeSearchContext();
            string elementToSearch = _target.SourceCollection.ElementAt(5);

            ICommand next = _target.SearchNextCommand;
            ICommand prev = _target.SearchPrevCommand;

            _target.SearchSpecification = elementToSearch;

            string selectedItem = null;
            _target.SelectItem = (item) =>
            {
                selectedItem = item;
            };

            next.Execute(null);
            prev.Execute(null);
            prev.Execute(null);
            prev.Execute(null);

            next.Execute(null);

            Assert.IsNotNull(selectedItem, "SelectItem not found!");
            Assert.IsFalse(_target.HasError);
        }

        [Test]
        public void Reset_command_also_resets_error()
        {
            initializeSearchContext();
            string elementToSearch = _target.SourceCollection.ElementAt(5);

            ICommand next = _target.SearchNextCommand;
            ICommand reset = _target.ResetCommand;

            _target.SearchSpecification = elementToSearch;

            string selectedItem = null;
            _target.SelectItem = (item) =>
            {
                selectedItem = item;
            };

            next.Execute(null);
            next.Execute(null);

            reset.Execute(null);

            Assert.IsFalse(_target.HasError);
        }

        #region Internals

        private void initializeSearchContext(int pos, string overrideValue)
        {
            Assert.That(pos, Is.InRange(0, 19));
            initializeSearchContext();

            string[] coll = (string[])_target.SourceCollection;
            coll[pos] = overrideValue;
        }

        private void initializeSearchContext()
        {
            List<string> list = new List<string>();
            for (int i = 0; i < 20; i++)
            {
                list.Add(new string(((char)(64 + i)), 4));
            }

            _target.SourceCollection = list.ToArray();
            _target.FilterSetGeneration = (sourceColl, filterSpec) =>
                {
                    return sourceColl.Where(p => string.CompareOrdinal(p, filterSpec) == 0);
                };

            _target.SelectItem = i => { };
        }

        #endregion Internals
    }
}